﻿/// <reference path='scripts/jquery-1.4.4.js'/>

(function (win, $, undef) {
    /// <param name='$' type='jQuery'/>
    /// <param name='win' type='window'/>

    var ex = {
        toString: function () {
            return '[Enumerable]';
        }
    };

    // caching
    var slice = Array.prototype.slice;
    var isArray = $.isArray;
    var extend = $.extend;
    var concat = Array.prototype.concat;
    var toString = Object.prototype.toString;
    var lookupDeleteToken = {};
    var counters = {};
    var counterStack = [];
    var formatUtilities = {};

    var defaults = {
        valueParamName: 'x',
        indexParamName: 'i',
        levelParamName: 'l',
        seedParamName: 's',
        previousParamName: 'p',
        rowParamName: 'r',
        columnParamName: 'c',
        groupKeyParamName: 'k',
        groupEntryParamName: 'e',
        joinFirstParamName: 'x',
        joinSecondParamName: 'y',
        comparerFirstParamName: 'a',
        comparerSecondParamName: 'b',
        formatUtilitiesParamName: 'u',
        globalizeParamName: 'g',
        jQueryInjectionMethods: ['addClass', 'removeClass', 'fadeIn', 'fadeOut', 'show', 'hide', 'toggle'],
        formatUtilities: formatUtilities
    };

    var converters = {
        query: {
            init: function (params, origin) {
                if (typeof origin == 'string') {
                    params.name = origin;
                }
                params.nameSelector = toFunction(params.nameSelector);
                params.valueSelector = toFunction(params.valueSelector) || (params.name ? delegates.returnFirst : null);
                params.encoder = params.encoder || win.encodeURIComponent || win.encodeURI || win.escape;
                if (params.index === true) {
                    params.index = 0;
                }
                else if (params.index === false) {
                    params.index = -1;
                }
                else {
                    params.index = typeof params.index == 'undefined' ? -1 : parseInt(params.index, 10) || 0;
                    if (params.name && params.index == -1 && /\[\?]/.test(params.name.toString())) {
                        params.index = 0;
                        params.name = params.name.toString().replace(/\[\?]/, '[]');
                    }
                }
            },

            convert: function (v, i, params) {
                if (v == null) return null;
                var value = params.encoder(params.valueSelector ? params.valueSelector(v, i) : v.value);
                if (params.name) {
                    if (params.index > -1) {
                        return [params.name.toString().replace(/\[]/, '[' + (params.index + i) + ']'), '=', value].join('');
                    }
                    else {
                        return [params.name, '=', value].join('');
                    }
                }
                else {
                    var name = params.encoder(params.nameSelector ? params.nameSelector(v, i) : v.name);
                    return [name, '=', value].join('');
                }
            },

            output: function (e) {
                return e.notNull().implode('&');
            }
        }
    };

    var enumeratorSignature = {};

    var delegates = {
        returnTrue: function () { return true; },
        returnFalse: function () { return false; },
        returnNothing: function () { return; },
        returnEmpty: function () { return ''; },
        returnThis: function () { return this; },
        returnFirst: function (a) { return a; },
        returnSecond: function (a, b) { return b; },
        returnKey: function (x) { return x == null ? null : x.key; },
        returnValue: function (x) { return x == null ? null : x.value; }
    };

    var fibNumbers = [0, 1];
    var primeNumbers = {};

    var generators = {
        query: function (window, returnObj) {
            if (typeof window == 'boolean') return this.query(win, window);
            if (!window) window = win;
            var parsedQuery = window.__parsedQueryString;
            if (!parsedQuery) {
                window.__parsedQueryString = parsedQuery = {};
                var href = (location.href || '').toString();
                var index = href.indexOf('#');
                if (index != -1) href = href.substr(0, index);
                index = href.indexOf('?');
                if (index != -1) href = href.substr(index + 1);
                var array = [];
                var obj = {};
                var decoder = window.encodeURIComponent || window.encodeURI || window.escape;
                var parts = href.split('&');
                for (var i = 0; i < parts.length; i++) {
                    var part = parts[i];
                    if (!part) continue;
                    index = part.indexOf('=');
                    var name, value;
                    if (index == -1) {
                        name = decoder(part);
                        value = '';
                    }
                    else {
                        name = decoder(part.substr(0, index));
                        value = decoder(part.substr(index + 1));
                    }
                    array.push({ name: name, value: value });
                    var prop = obj[name];
                    if (typeof prop == 'string') {
                        obj[name] = [prop, name];
                    }
                    else if (prop instanceof Array) {
                        prop.push(name);
                    }
                    else {
                        obj[name] = value;
                    }
                }
                parsedQuery.array = array;
                parsedQuery.hash = obj;
            }

            return returnObj ? parsedQuery.hash : linq(parsedQuery.array);
        },

        cycle: function () {
            if (arguments.length == 0) return emptyEnumerable();
            if (arguments.length == 1 && isArray(arguments[0])) return this.cycle.apply(this, arguments[0]);
            var args = toArray(arguments);
            return new enumerableClass(function () {
                var index = 0;
                return createEnumerator(function () {
                    if (index >= args.length) index = 0;
                    return yield(args[index++]);
                });
            }, null, args);
        },

        revCycle: function () {
            if (arguments.length == 0) return emptyEnumerable();
            if (arguments.length == 1 && isArray(arguments[0])) return this.revCycle.apply(this, arguments[0]);
            var args = toArray(arguments);
            return new enumerableClass(function () {
                var index = -1;
                return createEnumerator(function () {
                    if (index < 0) index = args.length - 1;
                    return yield(args[index--]);
                });
            }, null, args);
        },

        choice: function () {
            if (arguments.length == 0) return emptyEnumerable();
            if (arguments.length == 1 && isArray(arguments[0])) return this.choice.apply(this, arguments[0]);
            var args = toArray(arguments);
            return new enumerableClass(function () {
                return createEnumerator(function () {
                    return yield(args[Math.floor(Math.random() * args.length)]);
                });
            }, null, args);
        },

        until: function (callback, terminal, context) {
            return new enumerableClass(function () {
                var count = 0;
                return createEnumerator(function () {
                    var result = callback.call(context, count++);
                    if (result != terminal) return yield(result);
                    count++;
                });
            });
        },

        props: function (obj) {
            return linq([obj]).props().selectMany();
        },

        attrs: function (element) {
            return linq([element]).attrs().selectMany();
        },

        jq: function (expression) {
            /// <returns type='Enumerable'/>
            return new enumerableClass(function () {
                return jqueryToEnumerator($(expression));
            });
        },

        range: function (from, to, step, isDown) {
            /// <returns type='Enumerable'/>
            if (typeof to == 'boolean') {
                // overload: range(to, isDown);
                return arguments.callee(0, (parseFloat(from) || 0) - 1, 1, to);
            }
            from = parseFloat(from, 10) || 0;
            to = parseFloat(to, 10) || 0;
            if (isDown == null) {
                // overload: range(from, to, isDown)
                if (typeof (step) == 'boolean') {
                    isDown = step;
                    step = 1;
                }
            }
            return new enumerableClass(function () {
                return rangeToEnumerator(from, to, step, isDown);
            });
        },

        infinite: function (start, step, isDown) {
            if (start instanceof Function) {
                return new enumerableClass(function () {
                    var index = 0;
                    return createEnumerator(function () {
                        return yield(start(index++));
                    });
                });
            }
            /// <returns type='Enumerable'/>
            // overload: infinite()
            if (arguments.length == 0) return arguments.callee(0, 1, false);
            // overload: infinite(start, isDown)
            if (typeof step == 'boolean') return arguments.callee(start, 1, step);

            return linq.range(start, Number.MAX_VALUE, step, isDown);
        },

        from: function () {
            /// <summary>Return linq object from passed arguments</summary>
            /// <returns type="Enumerable" />
            if (arguments.length == 0) return emptyEnumerable();
            return linq(toArray(arguments));
        },

        repeat: function (value, count) {
            /// <returns type='Enumerable'/>
            count = Math.abs(parseInt(count, 10) || 1);
            if (count == 1) return linq([value]);
            return new enumerableClass(function () {
                var e = rangeToEnumerator(0, count - 1);
                extend(e, {
                    get: function (i) {
                        i = parseInt(i, 10) || 0;
                        return i >= 0 && i < count ? value : undef;
                    },
                    count: function () {
                        return count;
                    }
                });
                return e;
            });
        },

        fibs: function () {
            if (arguments.length > 0) return this.fibs().get(Math.abs(parseInt(arguments[0]) || 0));
            return new enumerableClass(function () {
                var index = 0;
                return createEnumerator(function () {
                    var result = 0;
                    if (index > 1) {
                        result = fibNumbers[index];
                        if (!result) {
                            fibNumbers[index] = result = fibNumbers[index - 1] + fibNumbers[index - 2];
                        }
                    }
                    else {
                        result = fibNumbers[index];
                    }
                    index++;
                    return yield(result);
                });
            });
        },

        primes: function () {
            if (arguments.length > 0) {
                var checkNumber = parseInt(arguments[0], 10) || 0;
                if (checkNumber < 2) return false;
                var existed = primeNumbers[checkNumber];
                if (existed) return true;
                for (var n = 2; n < checkNumber; n++) {
                    if (checkNumber % n == 0) {
                        return false;
                    }
                }
                primeNumbers[checkNumber] = checkNumber;
                return true;
            }
            return new enumerableClass(function () {
                var max = Number.MAX_VALUE;
                var index = 2;
                var last = 1;
                return createEnumerator(function () {
                    var result = primeNumbers[index] == index;
                    if (!result) {
                        for (var i = last + 1; i < max; i++) {
                            var found = false;
                            for (var j = 2; j < i; j++) {
                                if (i % j == 0) {
                                    found = true;
                                    break;
                                }
                            }
                            if (found) continue;
                            primeNumbers[index] = result = i;
                            break;
                        }
                    }
                    index++;
                    return yield(last = result);
                });
            });
        },

        random: function (min, max, real) {
            /// <returns type="Enumerable" />
            // random()
            if (arguments.length == 0) return this.random(0, Number.MAX_VALUE, false);
            if (arguments.length == 1) {
                // random(real)
                if (typeof min == 'boolean') return this.random(0, Number.MAX_VALUE, min);
                // random(max)
                return this.random(0, min, false);
            }
            if (arguments.length == 2) {
                // random(max, real)
                if (typeof max == 'boolean') return this.random(0, min, max);
                // random(min, max)
                return this.random(min, max, false);
            }
            // random(min, max, real)
            min = parseFloat(min) || 0;
            max = parseFloat(max) || 0;
            if (min < 0) min = 0;
            if (max < 0) max = 0;
            if (min == max) return min;
            return this.infinite(real ? function () {
                return min + Math.random() * (max - min);
            } : function () {
                return min + Math.floor(Math.random() * (max - min));
            });
        }
    };


    var enumerableClass = win.Enumerable = function (e, getter, value) {
        if (!(e instanceof Function)) throw notSupportedException();
        this.enumerator = function () {
            return e.call(this, value);
        };

        this.value = function () {
            if (getter instanceof Function) return getter.call(this);
            return value;
        };
    };

    function getHashCode(value) {
        /// <returns type="String" />
        if (value === null) return "null";
        if (value === undefined) return "undefined";

        return typeof value.toString === 'function' ? value.toString() : toString.call(value);
    }

    var lookup = function (comparer, keyRemoveToken) {
        /// <returns type="Object" />
        comparer = toComparer(comparer);
        var dictionary = {};
        return function (key) {
            // lookup():Array
            if (arguments.length == 0) {
                var keys = [];
                $.each(dictionary, function (name, value) {
                    for (i = 0; i < value.length; i++) {
                        keys.push(value[i].key);
                    }
                });
                return keys;
            }
            var hash = getHashCode(key);
            var entry, e, i;
            // lookup(key):Array
            if (arguments.length == 1) {
                entry = dictionary[hash];
                if (!entry) return null;
                for (i = 0; i < entry.length; i++) {
                    e = entry[i];
                    if (comparer(e.key, key)) return e.items;
                }
                return null;
            }
            // lookup(key, values...):Function;
            entry = dictionary[hash];
            var items = slice.call(arguments, 1);
            var isDeleteCommand = arguments.length == 2 && arguments[1] === keyRemoveToken;
            if (!entry) {
                if (isDeleteCommand) return arguments.callee;
                if (!entry) dictionary[hash] = entry = [];
            }
            for (i = 0; i < entry.length; i++) {
                e = entry[i];
                if (comparer(e.key, key)) {
                    if (isDeleteCommand) {
                        entry.splice(i, 1);
                        break;
                    }
                    else {
                        e.items = e.items.concat(items);
                    }

                    return arguments.callee;
                }
            }
            if (!isDeleteCommand) entry.push({ key: key, items: items });
            return arguments.callee;
        };
    };

    enumerableClass.prototype = ex;

    var enumeratorResultClass = function (value, code) {
        this.value = value;
        this.code = code;
    };

    extend(enumeratorResultClass, {
        Unknown: 0,
        Break: 1,
        Ignored: 2
    });


    function isType(instance, type, allowNull) {
        if (type == null) return true;
        if (typeof type == 'string') {
            return typeof instance === type;
        }
        return instance == null ? false || allowNull : instance instanceof type || instance.constructor === type;
    }

    function getComparerParams() {
        return [defaults.comparerFirstParamName, defaults.comparerSecondParamName];
    }

    function getJoinParams() {
        return [defaults.joinFirstParamName, defaults.joinSecondParamName, defaults.indexParamName];
    }

    function getGroupParams() {
        return [defaults.groupKeyParamName, defaults.groupEntryParamName, defaults.indexParamName];
    }

    function getAggrParams() {
        return [defaults.seedParamName, defaults.valueParamName, defaults.indexParamName];
    }

    function getSelectorParams() {
        return [defaults.valueParamName, defaults.indexParamName];
    }

    function getNestingParams() {
        return [defaults.valueParamName, defaults.indexParamName, defaults.levelParamName];
    }

    function getMatrixParams() {
        return [defaults.valueParamName, defaults.indexParamName, defaults.rowParamName, defaults.columnParamName];
    }

    function notSupportedException() {
        return new Error('Operator was not supported');
    }

    function yieldResult(value) {
        this.value = value;
    }

    function yield(value) {
        if (arguments.length == 2) {
            if (value) return new yieldResult(arguments[1]());
            return undef;
        }
        return new yieldResult(value);
    }

    function createEnumerator(next, properties) {
        var atEnd = false;
        var index = -1;
        if (!(next instanceof Function)) throw notSupportedException();
        var current;
        var e = {
            __type: enumeratorSignature,
            proxy: function (callback) {
                var me = this;
                return function () {
                    return callback.apply(me, arguments);
                };
            },
            next: function () {
                if (atEnd) return false;
                current = next.call(this, yield);
                if (current instanceof yieldResult) {
                    index++;
                    current = current.value;
                }
                else {
                    atEnd = true;
                }
                return !atEnd;
            },
            index: function () {
                return index;
            },
            reset: function () {
                index = -1;
                atEnd = false;
                current = undef;
            },

            result: function (value, code) {
                return new enumeratorResultClass(value, code);
            },

            ignore: function () {
                return this.result(null, enumeratorResultClass.Ignored);
            },

            each: function (callback, collect) {
                var results = [];
                var count = 0;
                this.reset();
                while (this.next()) {
                    var c = this.current();
                    var i = this.index();
                    var result = callback ? c : callback.call(this, c, i);
                    if (result === false) result = this.result(c, enumeratorResultClass.Break);
                    if (!(result instanceof enumeratorResultClass)) result = this.result(result, enumeratorResultClass.Unknown);
                    var isBreak = false;
                    switch (result.code) {
                        case enumeratorResultClass.Break:
                            isBreak = true;
                            break;
                    }
                    if (isBreak) break;
                    if (collect && result.code != enumeratorResultClass.Ignored) results[results.length] = result.value;
                }
                return collect ? results : count;
            },
            current: function () {
                return atEnd ? undef : current;
            },
            toString: function () {
                return '[Enumerator]';
            }
        };

        if (properties) extend(e, properties);
        return e;
    }

    function arrayToEnumerator(array, selector) {
        var index = -1;
        return createEnumerator(function () {
            if (++index < array.length) return yield(selector ? selector(index, array) : array[index]);
        });
    }

    function stringToEnumerator(value) {
        var length = value.length;
        var index = -1;
        return createEnumerator(function () {
            if (++index < length) return yield(value.charAt(index));
        });
    }

    function rangeToEnumerator(from, to, isDown, step) {
        var index = from + (isDown ? 1 : -1);
        step = Math.abs(parseFloat(step)) || 1;
        return createEnumerator(function () {
            if (isDown) {
                index -= step;
                if (index <= from && index >= to) return yield(index);
            }
            index += step;
            if (index >= from && index <= to) return yield(index);
        });
    }

    function jqueryToEnumerator(jquery) {
        var index = -1;
        return createEnumerator(function () {
            if (++index < jquery.length) return yield(jquery.get(index));
        });
    }

    function emptyEnumerator() {
        return createEnumerator(delegates.returnNothing);
    }

    function emptyEnumerable() {
        return new enumerableClass(emptyEnumerator, null, undef);
    }

    function createEnumerableProxy(enumerable, enumeratorSelector, properties) {
        var e = new enumerableClass(function () {
            return enumeratorSelector.call(enumerable, enumerable.enumerator(), enumerable);
        });
        if (properties) {
            extend(e, properties);
        }
        return e;
    }

    function isLambda(expression) {
        return !/^\s*{{/.test(expression);
    }

    function toFunction(expression, params) {
        /// <returns type="Function" />
        if (typeof expression == 'function') return expression;
        if (typeof expression == 'string') {
            if (expression.charAt(0) == '#') return delegates[expression.substr(1)];
            if (expression.charAt(0) == '@') expression = 'arguments[0]["' + expression.substr(1) + '"]';
            var results = /([$\w\s,_]*)=>([\w\W]*)/i.exec(expression);
            if (results) {
                params = (results[1] == null ? '' : results[1]).replace(/\s+/g, '');
                if (params) {
                    params = params.split(',');
                    for (var i = 0; i < params.length; i++) {
                        if (!params[i]) params[i] = '__param__' + i;
                    }
                }
                else {
                    params = null;
                }
                expression = results[2] == null ? '' : results[2];
            }
            if (!params) params = getSelectorParams();
            params.push(isLambda(expression) ? 'return ' + expression : expression);
            return Function.apply(null, params);
        }
        return undef;
    }

    function defaultComparer(a, b) {
        return a == b;
    }

    function exactComparer(a, b) {
        return a === b;
    }

    function defaultAscComparer(a, b) {
        if (a == b) return 0;
        if (a > b) return 1;
        return -1;
    }

    function defaultDescComparer(a, b) {
        if (a == b) return 0;
        if (a > b) return -1;
        return 1;
    }

    function addOrder(orders, isThenBy, selector, isDescending) {
        selector = toFunction(selector) || delegates.returnFirst;
        var comparer = typeof isDescending == 'function' ? isDescending : isDescending ? defaultDescComparer : defaultAscComparer;
        if (isThenBy) {
            var last = orders[orders.length - 1];
            last.push({ selector: selector, comparer: comparer });
        }
        else {
            orders.push([{ selector: selector, comparer: comparer}]);
        }
    }

    function toArray(obj) {
        if (obj instanceof Array) return obj;
        if (typeof obj != 'string' && obj.length) return slice.call(obj);
        return null;
    }

    function toFunctionEx(expression, params, defDelegate, trueDelegate, falseDelegate) {
        if (expression == null) return defDelegate;
        if (expression instanceof Function) return expression;
        if (typeof expression == 'string') return toFunction(expression, params) || defDelegate;
        return (expression ? trueDelegate : falseDelegate) || defDelegate;
    }

    function toComparer(expression, def) {
        return toFunctionEx(expression, getComparerParams(), def || defaultComparer, defaultComparer, exactComparer);
    }

    function toKeyComparer(expression, def) {
        if (isArray(expression)) {
            if (expression.length) {
                for (var j = 0; j < expression.length; j++) {
                    expression[j] = toComparer(expression[j]);
                }
                return function (a, b) {
                    if (a == null && b == null) return true;
                    if (a == null || b == null) return false;
                    if (a.length != b.length) return false;
                    for (var i = 0; i < a.length; i++) {
                        var comparer = expression[i] || defaultComparer;
                        if (!comparer(a[i], b[i])) return false;
                    }
                    return true;
                };
            }
            else {
                return function (a, b) {
                    if (a == null && b == null) return true;
                    if (a == null || b == null) return false;
                    if (a.length != b.length) return false;
                    for (var i = 0; i < a.length; i++) {
                        if (!defaultComparer(a[i], b[i])) return false;
                    }
                    return true;
                };
            }
        }
        else {
            return toComparer(expression, def);
        }
    }

    function trySelect(enumerable, isNew, selector, elementType, params) {
        if (!isNew && enumerable.__element && elementType && enumerable.__element === elementType) return enumerable;
        if (!isNew && enumerable.__addSelector) {
            enumerable.__addSelector(selector);
        }
        else {
            enumerable = enumerable.select(selector);
        }
        if (elementType) enumerable.__element = elementType;
        if (params) {
            extend(enumerable, params);
        }
        return enumerable;
    }

    function isEnumerable(value) {
        return value instanceof enumerableClass;
    }

    var linq = function (e) {
        /// <returns type='Enumerable'/>
        if (e == null || e == undef) return emptyEnumerable();
        if (isEnumerable(e)) return e;
        if (e.__type === enumeratorSignature) return new enumerableClass(e);
        if (typeof e == 'string') return new enumerableClass(stringToEnumerator, undef, e);
        if (typeof e == 'number') {
            if (e == 0) return emptyEnumerable();
            var isDown = arguments[1];
            var step = arguments[2];
            if (typeof isDown == 'number') {
                step = isDown;
                isDown = arguments[2];
            }

            if (isDown) {
                return new enumerableClass(function () {
                    return rangeToEnumerator(e - 1, 0, isDown, step);
                });
            }
            else {
                return new enumerableClass(function () {
                    return rangeToEnumerator(0, e - 1, isDown, step);
                });
            }

        }
        if (isArray(e)) return new enumerableClass(arrayToEnumerator, undef, e);
        if (!isNaN(e.length)) return linq(toArray(e));
        return linq([e]);
        //return new enumerableClass(objectToEnumerator, undef, e);
    };




    linq.extensions = extend(ex, {
        array: function (recursive) {
            /// <returns type='Array'/>
            var value = this.value();
            if (value instanceof Array) {
                if (recursive) {
                    for (var i = 0; i < value.length; i++) {
                        var item = value[i];
                        if (isEnumerable(item)) value[i] = item.array(true);
                    }
                }
                return value;
            }
            value = [];
            var e = this.enumerator();
            while (e.next()) {
                var current = e.current();
                if (recursive && isEnumerable(current)) current = current.array(true);
                value[value.length] = current;
            }
            return value;
        },

        contains: function (value, comparer) {
            /// <returns type='Boolean'/>
            return this.indexOf(value, comparer) != -1;
        },

        def: function (value) {
            /// <returns type='Enumerable'/>
            return createEnumerableProxy(this, function (e) {
                var isEmpty = false;
                var isFirst = true;
                return createEnumerator(function () {
                    if (isEmpty) return;
                    if (e.next()) {
                        isFirst = false;
                        return yield(e.current());
                    }
                    if (isFirst) {
                        isEmpty = true;
                        isFirst = false;
                        return value;
                    }
                });
            });
        },

        lookup: function (keySelector, valueSelector, comparer) {
            comparer = toComparer(comparer);
            keySelector = toFunction(keySelector) || delegates.returnKey;
            valueSelector = toFunction(valueSelector) || delegates.returnValue;
            var result = lookup(comparer, lookupDeleteToken);
            var keys;
            this.each(function (x, i) {
                var key = keySelector(x, i);
                var value = valueSelector(x, i);
                result(key, value);
            });
            return {
                add: function () {
                    result.apply(null, arguments);
                    return this;
                },
                remove: function (key) {
                    result(key, lookupDeleteToken);
                    return this;
                },
                keys: function () {
                    if (!keys) keys = result();
                    return [].concat(keys);
                },
                count: function () {
                    return this.keys().length;
                },
                pairs: function () {
                    var me = this;
                    return new enumerableClass(function () {
                        var index = 0;
                        if (!keys) keys = me.keys();
                        return createEnumerator(function () {
                            if (index < keys.length) {
                                var key = keys[index];
                                var value = result(key);
                                index++;
                                return yield({ key: key, value: value });
                            }
                        });
                    });
                },
                enumerator: function () {
                    return this.pairs().enumerator();
                },
                get: function (key) {
                    return result(key);
                }
            };
        },

        join: function (inner, outerKeySelector, innerKeySelector, resultSelector, comparer) {
            /// <returns type="Enumerable" />
            inner = linq(inner) || emptyEnumerable();
            innerKeySelector = toFunction(innerKeySelector) || delegates.returnKey;
            outerKeySelector = toFunction(outerKeySelector) || delegates.returnKey;
            resultSelector = toFunction(resultSelector, getJoinParams()) || delegates.returnFirst;
            comparer = toKeyComparer(comparer);
            return createEnumerableProxy(this, function (e) {
                var innerLookup = inner.lookup(innerKeySelector, delegates.returnFirst, comparer);
                var items;
                var index = 0;
                return createEnumerator(function () {
                    while (true) {
                        if (items) {
                            if (index < items.length) {
                                var result = resultSelector(e.current(), items[index], e.index());
                                index++;
                                return yield(result);
                            }
                            index = 0;
                            items = null;
                        }
                        if (e.next()) {
                            var key = outerKeySelector(e.current(), e.index());
                            items = innerLookup.get(key);
                        }
                    }
                });
            });
        },

        group: function (keySelector, valueSelector, resultSelector, comparer) {
            /// <returns type="Enumerable" />
            keySelector = toFunction(keySelector) || delegates.returnKey;
            valueSelector = toFunction(valueSelector) || delegates.returnValue;
            resultSelector = toFunction(resultSelector, getGroupParams()) || delegates.returnSecond;
            comparer = toKeyComparer(comparer);
            var me = this;
            return new enumerableClass(function () {
                var e = me.lookup(keySelector, valueSelector, comparer).enumerator();
                return createEnumerator(function () {
                    while (e.next()) {
                        var current = e.current();
                        return yield(resultSelector(current.key, current, e.index()));
                    }
                });
            });
        },

        where: function (predicate) {
            /// <returns type='Enumerable'/>
            predicate = toFunction(predicate);
            if (!predicate) return this;
            if (this.__addPredicate) {
                this.__addPredicate(predicate);
                return this;
            }
            var predicates = [predicate];
            return createEnumerableProxy(this, function (e, me) {
                return createEnumerator(function () {
                    while (e.next()) {
                        var current = e.current();
                        var index = e.index();
                        var ok = true;
                        for (var i = 0; i < predicates.length; i++) {
                            if (!predicates[i].call(me, current, index)) {
                                ok = false;
                                break;
                            }
                        }
                        if (ok) return yield(current);
                    }
                });
            }, {
                __addPredicate: function () {
                    predicates = predicates.concat(toArray(arguments));
                }
            });
        },

        filter: function (predicate, selector) {
            selector = toFunction(selector) || delegates.returnFirst;
            return this.where(function (v, i) {
                var oldValue = v;
                v = selector(v, i);
                return predicate(v, i, oldValue);
            });
        },

        isNull: function (exact, selector) {
            /// <returns type='Enumerable'/>
            return this.filter(function (v) {
                return exact ? v === null : v == null;
            }, selector);
        },

        notNull: function (exact, selector) {
            /// <returns type='Enumerable'/>
            return this.filter(function (v) {
                return exact ? v !== null : v != null;
            }, selector);
        },

        regex: function () {
            /// <returns type="Enumerable" />
            return this.select(function (x) {
                return {
                    value: x == null ? '' : x.toString(),
                    match: function (pattern, options) {
                        if (!(pattern instanceof RegExp)) pattern = RegExp(pattern, options);
                        return pattern.exec(this.value) || [];
                    },
                    test: function (pattern, options) {
                        if (!(pattern instanceof RegExp)) pattern = RegExp(pattern, options);
                        return pattern.test(this.value);
                    }
                };
            });
        },

        test: function (pattern, selector) {
            /// <returns type='Enumerable'/>
            if (!pattern) return this;
            if (typeof pattern == 'string') pattern = new RegExp(pattern);
            return this.filter(function (v) {
                return pattern.test(v == null ? '' : v.toString());
            }, selector);
        },

        skip: function (count) {
            /// <returns type='Enumerable'/>
            return this.limit(count, -1);
        },

        take: function (count, fromLast) {
            /// <returns type='Enumerable'/>
            if (fromLast) {
                count = Math.abs(parseInt(count, 10)) || 0;
                return createEnumerableProxy(this, function (e) {
                    var enumerator, array;
                    return createEnumerator(function () {
                        if (!enumerator) {
                            array = [];
                            while (e.next()) {
                                if (array.length == count) array.shift();
                                array.push(e.current());
                            }
                            enumerator = linq(array).enumerator();
                        }
                        if (enumerator.next()) return yield(enumerator.current());

                    });
                });
            }
            return this.limit(0, count);
        },

        limit: function (start, count) {
            /// <summary>
            ///
            /// </summary>
            /// <param name="start" type="Number" />
            /// <param name="count" type="Number" />
            /// <returns type='Enumerable'/>
            var endChecker, startChecker;

            if (typeof start == 'function') {
                startChecker = start;
            }
            else {
                start = parseInt(start, 10) || 0;
                if (start < 0) start = 0;
                startChecker = function (v, i) {
                    return i >= start;
                };
            }

            if (typeof count == 'function') {
                endChecker = count;
            }
            else {
                count = parseInt(count, 10) || 0;
                endChecker = function (v, i, c) {
                    return count == -1 ? false : c >= count;
                };
            }
            return createEnumerableProxy(this, function (e) {
                var itemCount = 0;
                var started = false;
                return createEnumerator(function () {
                    if (started) {
                        if (!e.next()) return false;
                        if (endChecker(e.current(), e.index(), itemCount)) return;
                        itemCount++;
                        return yield(e.current());
                    }
                    while (e.next()) {
                        var i = e.index();
                        var v = e.current();
                        if (startChecker(v, i, itemCount)) break;
                    }
                    itemCount++;
                    started = true;
                    return yield(e.current());
                });
            });
        },

        first: function (predicate) {
            /// <returns type='Object'/>
            return this.firstOrDef(undef, predicate);
        },

        firstOrDef: function (def, predicate) {
            /// <returns type='Object'/>
            predicate = toFunction(predicate);
            var enumerable = (predicate ? this.where(predicate) : this);
            var origin = enumerable.value();
            if (isArray(origin)) {
                return origin.length == 0 ? def : origin[0];
            }
            var e = enumerable.enumerator();
            while (e.next()) { return e.current(); }
            return def;
        },

        lastOrDef: function (def, predicate) {
            /// <returns type='Object'/>
            predicate = toFunction(predicate);
            var enumerable = (predicate ? this.where(predicate) : this);
            var origin = enumerable.value();
            if (isArray(origin)) {
                return origin.length == 0 ? def : origin[origin.length - 1];
            }
            var result = def;
            var e = enumerable.enumerator();
            while (e.next()) { result = e.current(); }
            return result;
        },

        last: function (predicate) {
            /// <returns type='Object'/>
            return this.lastOrDef(undef, predicate);
        },

        match: function (pattern, group) {
            /// <returns type='Enumerable'/>
            if (!pattern) return this;
            var resultSelector;
            if (!(group instanceof Function)) {
                group = parseInt(group, 10) || 0;
                resultSelector = function (match) {
                    if (group == -1) return match;
                    return match[group] || '';
                };
            }
            else {
                resultSelector = group;
            }
            if (typeof pattern == 'string') pattern = new RegExp(pattern);
            return this.select(function (v) {
                var match = pattern.exec(v == null ? '' : v.toString());
                return resultSelector(match);
            });
        },

        ofType: function (type) {
            /// <returns type='Enumerable'/>
            var predicate = type instanceof Function ? function (v) {
                return v && (v instanceof type || v.constructor === type || v.__constructor === type);
            } : function (v) {
                return typeof v === type || (v && v.__type === type);
            };
            return this.where(predicate);
        },

        notEmpty: function (selector) {
            /// <returns type='Enumerable'/>
            return this.filter(function (v) {
                return v == null ? false : v.toString().length > 0;
            }, selector);
        },

        notZero: function (selector) {
            /// <returns type='Enumerable'/>
            return this.filter(function (v) {
                return parseFloat(v) != 0;
            }, selector);
        },

        number: function (def) {
            /// <returns type='Enumerable'/>
            if (arguments.length === 0) def = 0;
            var converter = function (v) {
                if (typeof v == 'object') {
                    v = v.toString();
                }
                var isNumber = typeof v == 'number';
                if (isNumber) return v;
                if (isNaN(v)) return def;
                return parseFloat(v);
            };
            return trySelect(this, this.__numberConverterParams !== def, converter, Number, {
                __numberConverterParams: def
            });
        },

        string: function (toLower) {
            /// <returns type='Enumerable'/>
            var converter = arguments.length == 0 ? function (v) {
                return typeof v == 'string' ? v : v == null ? '' : v.toString();
            } : toLower instanceof Function ? function (v) {
                return toLower(typeof v == 'string' ? v : v == null ? '' : v.toString());
            } : toLower ? function (v) {
                return typeof v == 'string' ? v.toLowerCase() : v == null ? '' : v.toString().toLowerCase();
            } : function (v) {
                return typeof v == 'string' ? v.toUpperCase() : v == null ? '' : v.toString().toUpperCase();
            };
            return trySelect(this, this.__stringConverterParams !== toLower, converter, String, {
                __stringConverterParams: toLower
            });
        },

        select: function (selector) {
            /// <returns type='Enumerable'/>
            selector = toFunction(selector);
            if (!selector) return this;

            if (this.__addSelector) {
                this.__addSelector(selector);
                return this;
            }

            var selectors = [selector];

            return createEnumerableProxy(this, function (e, me) {
                return createEnumerator(function () {
                    if (e.next()) {
                        var value = e.current();
                        var index = e.index();
                        for (var i = 0; i < selectors.length; i++) {
                            value = selectors[i].call(me, value, index);
                        }
                        return yield(value);
                    }
                });
            }, {
                __addSelector: function () {
                    selectors = selectors.concat(toArray(arguments));
                }
            });
        },

        pairs: function (keySelector, valueSelector) {
            /// <returns type='Array'/>
            keySelector = toFunctionEx(keySelector, null, delegates.returnSecond);
            valueSelector = toFunctionEx(valueSelector, null, delegates.returnFirst);
            return this.aggr([], function (s, v, i) {
                s[s.length] = {
                    key: keySelector.call(this, v, i),
                    value: valueSelector.call(this, v, i)
                };
            });
        },

        list: function (nameSelector, valueSelector) {
            /// <returns type='Array'/>
            nameSelector = toFunctionEx(nameSelector, null, delegates.returnSecond);
            valueSelector = toFunctionEx(valueSelector, null, delegates.returnFirst);
            return this.aggr([], function (s, v, i) {
                s[s.length] = {
                    name: nameSelector.call(this, v, i),
                    value: valueSelector.call(this, v, i)
                };
            });
        },

        delProps: function () {
            var props = toArray(arguments);
            if (props.length == 0) return this;
            return this.each(function (x) {
                if (x != null) {
                    for (var i = 0; i < props.length; i++) {
                        delete x[props[i]];
                    }
                }
            });
        },

        hasProp: function (name) {
            return this.where(function (x) {
                return x == null ? false : typeof x[name] != 'undefined';
            });
        },

        hash: function (keySelector, valueSelector) {
            /// <returns type='Object'/>
            keySelector = toFunctionEx(keySelector, null, delegates.returnSecond);
            valueSelector = toFunctionEx(valueSelector, null, delegates.returnFirst);
            return this.aggr([], function (s, v, i) {
                s[keySelector.call(this, v, i)] = valueSelector.call(this, v, i);
            });
        },

        min: function (selector) {
            /// <returns type='Number'/>
            return this.select(selector).aggr(Number.MAX_VALUE, function (s, v) {
                return Math.min(s, v);
            });
        },

        max: function (selector) {
            /// <returns type='Number'/>
            return this.select(selector).aggr(Number.MIN_VALUE, function (s, v) {
                return Math.max(s, v);
            });
        },

        sum: function (selector) {
            /// <returns type='Number'/>
            return this.select(selector).number().aggr(0, function (s, v) {
                return s + v;
            });
        },

        aggr: function (seed, callback, selector) {
            /// <returns type='Enumerable'/>
            callback = toFunction(callback, getAggrParams());
            if (callback) {
                this.each(function (v, i) {
                    seed = callback(seed, v, i);
                });
                selector = toFunction(selector);
                if (selector) seed = selector(seed);
            }
            return seed;
        },

        sort: function (comparer) {
            /// <returns type='Enumerable'/>
            return this.orderBy(delegates.returnFirst, comparer);
        },

        call: function (method) {
            /// <returns type='Enumerable'/>
            var args = [];
            for (var i = 1; i < arguments.length; i++) {
                args[args.length] = arguments[i];
            }
            return this.apply(method, args);
        },

        apply: function (method, args) {
            /// <returns type='Enumerable'/>
            if (!args) args = [];
            var isFunc = method instanceof Function;
            return this.each(function (v) {
                var m = isFunc ? method : v ? v[method] : null;
                m.apply(v, args);
            });
        },

        map: function (addtionalParams, method) {
            /// <returns type='Enumerable'/>
            if (!(addtionalParams instanceof Array)) {
                var temp = addtionalParams;
                addtionalParams = [];
                for (var i = 1; i < arguments.length; i++) {
                    addtionalParams.push(arguments[i]);
                }
                method = temp;
            }

            if (typeof (method) == 'string') {
                return this.select(function (v) {
                    var m = v[method];
                    if (m instanceof Function) {
                        return m.apply(v, addtionalParams);
                    }
                    return undef;
                });
            }
            else {
                addtionalParams.unshift(null);
                return this.select(function (v) {
                    addtionalParams[0] = v;
                    return method.apply(v, addtionalParams);
                });
            }
        },

        equal: function (value, exact) {
            /// <returns type='Enumerable'/>
            var comparer = toComparer(exact);
            return this.where(function (v) {
                return comparer(v, value);
            });
        },

        not: function (value, exact) {
            /// <returns type='Enumerable'/>
            var comparer = toComparer(exact);
            return this.where(function (v) {
                return !comparer(v, value);
            });
        },

        round: function (decimals) {
            /// <returns type='Enumerable'/>
            decimals = parseInt(decimals, 10) || 0;
            return this.number().map('toFixed', decimals);
        },

        ceil: function () {
            /// <returns type='Enumerable'/>
            return this.number().map(Math.ceil);
        },

        floor: function () {
            /// <returns type='Enumerable'/>
            return this.number().map(Math.floor);
        },

        mul: function (value) {
            /// <returns type='Enumerable'/>
            value = parseFloat(value) || 0;
            return this.number(0, function (v) { return v * value; });
        },

        div: function (value) {
            /// <returns type='Enumerable'/>
            value = parseFloat(value) || 0;
            return this.number(0, function (v) { return value == 0 ? NaN : v / value; });
        },

        add: function (value) {
            /// <returns type='Enumerable'/>
            value = parseFloat(value) || 0;
            return this.number(0, function (v) { return v + value; });
        },

        sub: function (value) {
            /// <returns type='Enumerable'/>
            value = parseFloat(value) || 0;
            return this.number(0, function (v) { return v - value; });
        },

        odd: function () {
            /// <returns type='Enumerable'/>
            return this.oddBy(2);
        },

        even: function () {
            /// <returns type='Enumerable'/>
            return this.evenBy(2);
        },

        oddBy: function (value) {
            /// <returns type='Enumerable'/>
            value = parseInt(value, 10) || 0;
            if (value == 0) return emptyEnumerable();
            return this.where(function (v) {
                return v % value != 0;
            });
        },

        evenBy: function (value) {
            /// <returns type='Enumberable'/>
            value = parseInt(value, 10) || 0;
            if (value == 0) return emptyEnumerable();
            return this.where(function (v) {
                return v % value == 0;
            });
        },

        int: function (radix) {
            radix = parseInt(radix, 10) || 0;
            if (radix) {
                return this.select(function (x) {
                    return parseInt(x = null ? 0 : x.toString(), radix);
                });
            }
            return this.number().select(function (x) {
                return x.toFixed(0);
            });
        },

        range: function (min, max, mapValues) {
            min = min == null ? NaN : parseFloat(min);
            max = max == null ? NaN : parseFloat(max);
            var e = this.number();
            if (mapValues) {
                return e.select(function (x) {
                    if (min != NaN && x < min) return min;
                    if (max != NaN && x > max) return max;
                    return x;
                });
            }
            else {
                return e.where(function (x) {
                    return (min == NaN || x >= min) && (max == NaN || x <= max);
                });
            }
        },

        pick: function (def, excludeEmptyString) {
            /// <returns type="Enumerable" />
            return this.where(function (x) {
                return x != null && typeof x != 'undefined' && (!excludeEmptyString || x !== '');
            }).firstOrDef(def);
        },

        get: function (index) {
            /// <returns type='Object'/>
            if (arguments.length == 0) {
                var array = this.array();
                return array[Math.floor(Math.random() * array.length)];
            }
            index = parseInt(index, 10) || 0;
            var value = this.value();
            if (isArray(value)) return value[index];
            var e = this.enumerator();
            if (e.get) return e.get(index);
            while (e.next()) {
                if (e.index() == index) {
                    return e.current();
                }
            }
            return undef;
        },

        avg: function (selector) {
            /// <returns type='Number'/>
            var count = 0;
            var total = 0;
            selector = toFunction(selector);
            this.each(function (v, i) {
                total += parseFloat(selector ? selector(v, i) : v) || 0;
                count++;
            });
            return count == 0 ? NaN : total / count;
        },

        indexOf: function (value, comparer) {
            /// <returns type='Number'/>
            comparer = toComparer(comparer);
            var index = -1;
            this.each(function (v, i) {
                if (comparer(v, value)) {
                    index = i;
                    return false;
                }
                return true;
            });
            return index;
        },

        lastIndexOf: function (value, comparer) {
            /// <returns type='Number'/>
            comparer = toComparer(comparer);
            var array = this.array();
            for (var i = array.length - 1; i >= 0; i--) {
                if (comparer(array[i], value)) return i;
            }
            return -1;
        },

        orderBy: function (selector, isDescending) {
            /// <summary>
            /// &#10;1. orderBy(isDescending:Boolean = false)
            /// &#10;2. orderBy(selector:Function, isDescending:Boolean = false)
            /// &#10;3. orderBy(selector:Function, comparer:Function)
            /// </summary>
            /// <returns type='Enumerable'/>
            if (typeof selector == 'boolean') return this.orderBy(delegates.returnFirst, selector);
            if (this.__addOrderBy) {
                this.__addOrderBy(selector, isDescending);
                return this;
            }

            var orders = [];
            var me = this;
            var enumerable = new enumerableClass(function () {
                var array = me.array();
                for (var i = 0; i < orders.length; i++) {
                    var order = orders[i];
                    array.sort(function (a, b) {
                        for (var j = 0; j < order.length; j++) {
                            var obj = order[j];
                            var result = obj.comparer(obj.selector(a), obj.selector(b));
                            if (result != 0) return result;
                        }
                        return 0;
                    });
                }
                return arrayToEnumerator(array);
            });

            addOrder(orders, false, selector, isDescending);

            extend(enumerable, {
                __addOrderBy: function (s, d) {
                    addOrder(orders, false, s, d);
                },

                __addThenBy: function (s, d) {
                    addOrder(orders, true, s, d);
                }
            });

            return enumerable;
        },

        thenBy: function (selector, isDescending) {
            /// <summary>
            /// &#10;1. thenBy(isDescending:Boolean = false)
            /// &#10;2. thenBy(selector:Function, isDescending:Boolean = false)
            /// &#10;3. thenBy(selector:Function, comparer:Function)
            /// </summary>
            /// <returns type='Enumerable'/>
            if (typeof selector == 'boolean') return this.thenBy(delegates.returnFirst, selector);
            if (this.__addThenBy) {
                this.__addThenBy(selector, isDescending);
                return this;
            }
            return this.orderBy(selector, isDescending);
        },

        asc: function (selector, thenBy) {
            /// <returns type='Enumerable'/>
            if (thenBy) return this.thenBy(selector, false);
            return this.orderBy(selector, false);
        },

        desc: function (selector, thenBy) {
            /// <returns type='Enumerable'/>
            if (thenBy) return this.thenBy(selector, true);
            return this.orderBy(selector, true);
        },

        count: function (predicate) {
            /// <summary>
            /// &#10;1. count()
            /// &#10;2. count(predicate:String)
            /// &#10;3. count(predicate:Function)
            /// </summary>
            /// <returns type='Number'/>
            predicate = toFunction(predicate);
            if (predicate) {
                return this.where(predicate).count();
            }
            var value = this.value();
            if (isArray(value) || typeof value == 'string') return value.length;
            var e = this.enumerator();
            if (e.count) return e.count();
            var result = 0;
            while (e.next()) result++;
            return result;
        },

        all: function (predicate) {
            /// <returns type='Boolean'/>
            predicate = toFunction(predicate);
            var e = this.enumerator();
            if (!predicate) return this;
            while (e.next()) {
                if (!predicate.call(this, e.current(), e.index())) return false;
            }
            return true;
        },

        any: function (predicate) {
            /// <returns type='Boolean'/>
            predicate = toFunction(predicate);
            var e = this.enumerator();
            if (!predicate) {
                while (e.next()) return true;
                return false;
            }
            while (e.next()) {
                if (predicate.call(this, e.current(), e.index())) return true;
            }
            return false;
        },

        jq: function (predicate) {
            /// <returns type='jQuery'/>
            return $(this.where(predicate).array());
        },

        append: function () {
            /// <returns type='Enumerable'/>
            if (arguments.length == 0) return this;
            // increse performance
            if (this.isArray()) {
                return linq([].concat(this.value()).concat(toArray(arguments)));
            }
            return this.concat(arguments);
        },

        prepend: function () {
            /// <returns type='Enumerable'/>
            if (arguments.length == 0) return this;
            // increse performance
            if (this.isArray()) {
                return linq([].concat(toArray(arguments)).concat(this.value()));
            }
            return linq(arguments).concat(this);
        },

        exceptAll: function (items, comparer) {
            /// <returns type='Enumerable'/>
            if (!items) return this;
            comparer = toComparer(comparer);
            var enumerable = linq(items);
            return this.where(function (v) {
                return enumerable.indexOf(v, comparer) == -1;
            });
        },

        except: function () {
            /// <returns type='Enumerable'/>
            return this.exceptAll(toArray(arguments), defaultComparer);
        },

        implode: function (separator) {
            /// <returns type='String'/>
            return this.array().join(separator == null ? '' : separator);
        },

        isArray: function () {
            /// <returns type='Boolean'/>
            return isArray(this.value());
        },

        prop: function (name, value) {
            /// <summary>Use basic member accessor</summary>
            /// <returns type='Enumerable'/>
            if (arguments.length == 0) return this;
            if (arguments.length == 1) {
                if ($.isPlainObject(name)) {
                    return this.each(function (x) {
                        if (x != null) {
                            $.each(name, function (n, v) {
                                x[name] = v;
                            });
                        }
                    });
                }
                if (isArray(name)) {
                    return this.select(function (x) {
                        if (x == null) return new Array(name.length);
                        var result = [];
                        for (var i = 0; i < name.length; i++) {
                            result[i] = x[name[i]];
                        }
                        return result;
                    });
                }
                return this.select(function (v) { return v == null ? undef : v[name]; });
            }
            return this.each(function (v) {
                if (v != null) v[name] = value;
            });
        },

        selectMany: function (selector) {
            /// <returns type='Enumerable'/>
            if (selector) return this.select(selector).flatten(0);
            return this.flatten(0);
        },

        flatten: function (level) {
            /// <returns type='Enumerable'/>
            if (arguments.length == 0)
            // max level
                return this.flatten(0xFFFFFFFF);
            level = parseInt(level, 10) || 0;
            if (level < 0) return this;
            return createEnumerableProxy(this, function (e) {
                var currentEnumerator;
                var eof = false;
                return createEnumerator(function () {
                    var current;
                    if (eof) return;
                    if (currentEnumerator) {
                        if (currentEnumerator.next()) {
                            current = currentEnumerator.current();
                            return yield(current);
                        }
                        currentEnumerator = null;
                    }

                    while (currentEnumerator == null) {
                        if (!e.next()) break;
                        current = e.current();
                        if (!isArray(current) && !isEnumerable(current)) return yield(current);
                        currentEnumerator = linq(current).flatten(level - 1).enumerator();
                        if (currentEnumerator.next()) {
                            return yield(currentEnumerator.current());
                        }
                        currentEnumerator = null;
                    }
                    eof = true;
                });
            });
        },

        attr: function (name, value) {
            /// <summary>Use jQuery.attr</summary>
            /// <returns type='Enumerable'/>
            if (arguments.length == 0) return this;
            if (arguments.length == 1) {
                if ($.isPlainObject(name)) {
                    return this.each(function (v) {
                        $(v).attr(name);
                    });
                }
                if (isArray(name)) {
                    return this.select(function (x) {
                        if (x == null) return new Array(name.length);
                        var result = [];
                        for (var i = 0; i < name.length; i++) {
                            result[i] = $(x).attr(name[i]);
                        }
                        return result;
                    });
                }
                return this.select(function (v) { return v == null ? undef : $(v).attr(name); });
            }
            return this.each(function (v) {
                if (v != null) $(v).attr(name, value);
            });
        },

        reverse: function () {
            /// <returns type='Enumerable'/>
            var me = this;
            return new enumerableClass(function () {
                var array = me.array();
                array.reverse();
                return arrayToEnumerator(array);
            });
        },

        shuffle: function () {
            /// <returns type='Enumerable'/>
            var me = this;
            return new enumerableClass(function () {
                var array;
                return createEnumerator(function () {
                    if (array == null) array = me.array();
                    if (array.length > 0) {
                        var index = Math.floor(Math.random() * Math.random() * array.length);
                        var current = array[index];
                        array.splice(index, 1);
                        return yield(current);
                    }
                }, {
                    count: function () {
                        return array ? me.count() : array.length;
                    }
                });
            });
        },

        concat: function () {
            /// <returns type='Enumerable'/>
            if (arguments.length == 0) return this;
            var enumerables = [this];
            // build enumerable list
            for (var i = 0; i < arguments.length; i++) {
                var e = linq(arguments[i]);
                var last = enumerables[enumerables.length - 1];
                // if is array
                if (e.isArray() && last.isArray()) {
                    // concat two array;
                    var newArray = concat.call(last.value(), toArray(e.value()));
                    // create new enumerable base on concat array
                    e = linq(newArray);
                    // release last enumerable
                    enumerables.pop();
                }
                enumerables.push(e);
            }
            var count = enumerables.length;
            if (count == 1) return enumerables[0];

            return new enumerableClass(function () {
                var enumeratorIndex = -1;
                var currentEnumerator;
                return createEnumerator(function () {
                    if (enumeratorIndex >= count) return;
                    if (currentEnumerator) {
                        if (currentEnumerator.next()) return yield(currentEnumerator.current());
                        currentEnumerator = null;
                    }
                    if (!currentEnumerator) {
                        enumeratorIndex++;
                        if (enumeratorIndex >= count) return;
                        currentEnumerator = enumerables[enumeratorIndex].enumerator();
                    }
                    if (currentEnumerator.next()) return yield(currentEnumerator.current());
                });
            });
        },

        props: function () {
            /// <returns type="Enumerable" />
            return this.select(function (obj) {
                if (obj == null) return [];
                var array = [];
                $.each(obj, function (n, v) {
                    array[array.length] = { name: n, value: v };
                });
                return array;
            });
        },

        attrs: function () {
            /// <returns type="Enumerable" />
            return this.select(function (tag) {
                tag = tag != null ? $(tag).get(0) : tag;
                if (!tag || !tag.attributes) return [];
                var array = [];
                var attrs = tag.attributes;
                for (var i = 0; i < attrs.length; i++) {
                    var attr = attrs.item(i);
                    array[i] = { name: attr.nodeName, value: attr.nodeValue };
                }
                return array;
            });
        },

        each: function (callback) {
            /// <returns type='Enumerable'/>
            callback = toFunction(callback);
            if (callback) {
                var e = this.enumerator();
                while (e.next()) {
                    var result = callback.call(this, e.current(), e.index());
                    if (result === false) break;
                }
            }
            return this;
        },

        keys: function (def) {
            return this.select(function (x) {
                return x == null ? def : x.key;
            });
        },

        names: function (def) {
            return this.select(function (x) {
                return x == null ? def : x.name;
            });
        },

        values: function (def) {
            return this.select(function (x) {
                return x == null ? def : x.value;
            });
        },

        valueOf: function () {
            /// <returns type='Object'/>
            if (arguments.length == 0) return this.first();
            var v = this.first();
            if (v == null) return undef;
            return v[arguments[0]];
        },

        compact: function () {
            /// <returns type='Object'/>
            return this.where(function (x) {
                return x != null && x !== undefined && x !== '';
            });
        },

        find: function (value, comparer) {
            comparer = toFunctionEx(comparer);
            return this.where(function (x) {
                return comparer(value, x);
            });
        },

        partition: function () {
            /// <returns type='Array'/>
            var selectors = [];
            var results = [];
            var length = arguments.length;
            for (var i = 0; i < length; i++) {
                selectors[i] = toFunction(arguments[i]);
                results[i] = [];
            }
            this.each(function (value, index) {
                for (var j = 0; j < length; j++) {
                    var selector = selectors[j];
                    if (!selector) continue;
                    if (selector(value, index)) {
                        results[i].push(value);
                    }
                }
            });
            return results;
        },

        to: function (name, params) {
            var origin = params;
            if (!params || !$.isPlainObject(params)) {
                params = {};
            }
            var converter = typeof name == 'function' ? { convert: name} : converters[name];
            if (!converter) return this;
            if (converter.defaults) {
                extend(params, converter.defaults);
            }
            if (converter.init) converter.init(params, origin);
            var e = this;
            if (converter.convert) e = e.select(function (v, i) {
                return converter.convert(v, i, params);
            });
            if (converter.output) return converter.output(e, params);
            return e;
        },

        matrix: function (columns, selector) {
            /// <returns type='Enumerable'/>
            columns = parseInt(columns, 10) || 0;
            if (columns <= 0) columns = 1;
            selector = toFunction(selector, getMatrixParams()) || delegates.returnFirst;

            return createEnumerableProxy(this, function (e) {
                var current = undef;
                var row = -1;
                var column = -1;
                return createEnumerator(function () {
                    current = undef;
                    while (e.next()) {
                        if (!current) {
                            row++;
                            column = -1;
                            current = [];
                        }
                        column++;
                        current[current.length] = selector(e.current(), e.index(), row, column);
                        if (current.length == columns) break;
                    }
                    if (current) return yield(current);
                });
            });
        },

        merge: function (comparer) {
            /// <returns type='Enumerable'/>
            return this.concat.apply(this, typeof comparer == 'function' ? slice.call(arguments, 1) : arguments).unique();
        },

        clone: function () {
            /// <returns type='Enumerable'/>
            return linq(this.array());
        },

        trim: function () {
            /// <returns type='Enumerable'/>
            var converter = toFunctionEx(arguments[0], getSelectorParams(), function (x) {
                return x == null ? '' : x.toString().replace(/^\s+|\s+$/g, '');
            }, function (x) {
                return x == null ? '' : x.toString().replace(/\s+$/g, '');
            }, function (x) {
                return x == null ? '' : x.toString().replace(/^\s+/g, '');
            });
            return this.select(converter);
        },

        start: function (value, caseSensitive) {
            /// <returns type='Enumerable'/>
            value = value == null ? '' : value.toString();
            if (value.length == 0) return this;
            if (!caseSensitive) value = value.toLowerCase();
            var comparer = function (x) {
                return value.length <= x.length && (caseSensitive ? x.substr(0, value.length) : x.substr(0, value.length).toLowerCase()) == value;
            };
            return this.where(function (x) {
                return comparer(x == null ? '' : x.toString());
            });
        },

        breadth: function (childrenSelector) {
            /// <returns type='Enumerable'/>
            childrenSelector = toFunction(childrenSelector, getNestingParams()) || function (x) { return x.children; };
            return createEnumerableProxy(this, function (e) {
                var buffer = [];
                var enumerator = e;
                var level = 0;
                return createEnumerator(function () {
                    if (enumerator.next()) {
                        buffer.push(enumerator.current());
                        return yield(enumerator.current());
                    }
                    if (!buffer.length) return;
                    enumerator = linq(buffer).selectMany(function (x, i) {
                        return childrenSelector(x, i, level) || [];
                    }).enumerator();
                    buffer = [];
                    level++;
                    return arguments.callee();
                });
            });
        },

        depth: function (childrenSelector) {
            /// <returns type='Enumerable'/>
            childrenSelector = toFunction(childrenSelector, getNestingParams()) || function (x) { return x.children; };
            return createEnumerableProxy(this, function (e) {
                var stack = [];
                var enumerator = e;
                var level = 0;
                return createEnumerator(function () {
                    if (enumerator.next()) {
                        var current = enumerator.current();
                        var children = childrenSelector(current, enumerator.index(), level);
                        if (children) {
                            stack.push(enumerator);
                            enumerator = linq(children).enumerator();
                            level++;
                        }
                        return yield(current);
                    }
                    if (!stack.length) return;
                    enumerator = stack.pop();
                    level--;
                    return arguments.callee();
                });
            });
        },

        indent: function (indentSelector, indent, size) {
            /// <returns type='Enumerable'/>
            indentSelector = toFunction(indentSelector) || function (x) { return x.indent; };
            indent = (indent == null ? null : indent.toString()) || ' ';
            size = parseInt(size, 10) || 0;
            if (size <= 0) size = 4;
            indent = new Array(size + 1).join(indent);
            return this.select(function (x, i) {
                var v = parseInt(indentSelector(x, i), 10) || 0;
                return {
                    indents: v >= 0 ? new Array(v + 1).join(indent) : '',
                    value: x,
                    index: i
                };
            });
        },

        tree: function (idSelector, parentSelector, root, childAppender, comparer) {
            /// <returns type="Enumerable" />
            idSelector = toFunction(idSelector) || function (x) {
                return x != null ? x.id : null;
            };
            parentSelector = toFunction(parentSelector) || function (x) {
                return x != null ? x.parent : null;
            };
            comparer = toComparer(comparer);
            childAppender = toFunction(childAppender) || function (parent, child) {
                if (parent) {
                    if (!parent.children) {
                        parent.children = [];
                    }
                    parent.children.push(child);
                }
            };
            var array = this.array();
            var result = [];
            var find = function (parent, parentId) {
                for (var i = 0; i < array.length; i++) {
                    var child = array[i];
                    if (child == parent) continue;
                    var value = parentSelector(child);
                    if (comparer(value, parentId)) {
                        if (!parent) {
                            result.push(child);
                        }
                        else {
                            childAppender(parent, child);
                        }
                        find(child, idSelector(child));
                    }
                }
            };
            find(null, root);
            return linq(result);
        },

        format: function (pattern, separator, selector) {
            selector = toFunction(selector) || delegates.returnFirst;
            separator = separator == null ? '' : separator;
            var result = [];
            this.each(function (x, i) {
                result[result.length] = linq.format(selector(x, i), pattern);
            });
            return result.join(separator);
        },

        validate: function (validator, returnMessagesOnly, breakOnFirstError) {
            /// <returns type='Array'/>
            validator = toFunction(validator);
            var result = [];
            if (validator) {
                this.each(function (x, i) {
                    try {
                        validator(x, i);
                    }
                    catch (e) {
                        var error;
                        if (typeof ex == 'object' && typeof ex.message == 'undefined') {
                            error = ex;
                        }
                        else {
                            error = new Error(ex.toString());
                        }
                        if (returnMessagesOnly) {
                            result.push(error.message);
                        }
                        else {
                            result.push(error);
                        }
                        if (breakOnFirstError) return false;
                    }
                });
            }
            return result;
        },

        inter: function (other, comparer) {
            /// <summary>Intersect</summary>
            /// <returns type="Enumerable" />
            if (other == null) return this;
            other = linq(other);
            comparer = toComparer(comparer);
            return createEnumerableProxy(this, function (e, me) {
                var first;
                var second;
                var length;
                return createEnumerator(function () {
                    if (!first) {
                        first = me.array();
                        length = first.length;
                    }
                    if (!second) second = other.enumerator();
                    while (second.next()) {
                        var current = second.current();
                        for (var i = 0; i < length; i++) {
                            if (comparer(first[i], current)) return yield(current);
                        }
                    }
                });
            });
        },


        reduce: function (callback, init) {
            /// <returns type="Object" />
            callback = toFunction(callback, [defaults.previousParamName, defaults.valueParamName, defaults.indexParamName]) || delegates.returnSecond;
            var array = this.array();
            var length = array.length;
            if (length == 0) return init;
            var current, index;
            if (arguments.length > 1) {
                current = init;
                index = 0;
            }
            else {
                index = 1;
                current = array[0];
            }
            for (var i = index; i < length; i++) {
                current = callback.call(this, current, array[i], i);
            }
            return current;
        },

        reduceRight: function (callback, init) {
            /// <returns type="Object" />
            callback = toFunction(callback, [defaults.previousParamName, defaults.valueParamName, defaults.indexParamName]) || delegates.returnSecond;
            var array = this.array();
            var length = array.length;
            if (length == 0) return init;
            var current, index;
            if (arguments.length > 1) {
                current = init;
                index = length - 1;
            }
            else {
                index = length - 2;
                current = array[length - 1];
            }
            while (index >= 0) {
                current = callback.call(this, current, array[index], index);
                index--;
            }
            return current;
        },


        eval: function (exp) {
            /// <returns type="Enumerable" />
            return this.select(toFunction('x' + (exp == null ? '' : exp.toString())));
        },

        end: function (value, caseSensitive) {
            /// <returns type='Enumerable'/>
            value = value == null ? '' : value.toString();
            if (value.length == 0) return this;
            if (!caseSensitive) value = value.toLowerCase();
            var comparer = function (x) {
                return value.length <= x.length && (caseSensitive ? x.substr(x.length - value.length) : x.substr(x.length - value.length).toLowerCase()) == value;
            };
            return this.where(function (x) {
                return comparer(x == null ? '' : x.toString());
            });
        },

        mid: function (value, caseSensitive) {
            /// <summary>Contains string value</summary>
            /// <returns type='Enumerable'/>
            value = value == null ? '' : value.toString();
            if (value.length == 0) return this;
            if (!caseSensitive) value = value.toLowerCase();
            var comparer = function (x) {
                if (value.length != x.length) return false;
                return (caseSensitive ? x : x.toLowerCase()).indexOf(value) != -1;
            };
            return this.where(function (x) {
                return comparer(x == null ? '' : x.toString());
            });
        },

        matches: function (pattern, options) {
            /// <returns type="Enumerable" />
            if (pattern instanceof RegExp && !pattern.global) {
                pattern = eval(pattern + 'g');
            }
            else {
                pattern = new RegExp(pattern, options);
            }
            return this.select(function (x) {
                var matches = [];
                while (true) {
                    var result = pattern.exec(x == null ? '' : x.toString());
                    if (!result) break;
                    matches.push(result);
                }
                return matches;
            });
        },

        unique: function (comparer) {
            /// <returns type='Object'/>
            comparer = toComparer(comparer);
            return createEnumerableProxy(this, function (e) {
                var dic = [];
                return createEnumerator(function () {
                    while (e.next()) {
                        var current = e.current();
                        var found = false;
                        for (var i = 0; i < dic.length; i++) {
                            if (comparer(current, dic[i])) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            dic[dic.length] = current;
                            return yield(current);
                        }
                    }
                    return false;
                });
            });
        }
    });

    function inject(sourceObject, targetObject, useMethod, selector, mappingMethods) {
        $.each(mappingMethods, function (n, method) {
            targetObject[method] = function () {
                var args = arguments;
                return this[useMethod](function (value, index) {
                    var context = selector(value, index);
                    var m = sourceObject[method];
                    return m.apply(context, args);
                });
            };
        });
        return targetObject;
    }

    var libs = {
        // Html extensions
        html: function () {
            return inject($.fn, {
                jq: function (nullSelector) {
                    return this.select(function (v) {
                        return v == null ? nullSelector != null ? $(nullSelector) : $([]) : $(v);
                    });
                },

                enable: function (flag) {
                    return this.each(function (v) {
                        if (v) $(v).attr('disabled', !flag);
                    });
                },

                disable: function (flag) {
                    return this.each(function (v) {
                        if (v) $(v).attr('disabled', flag);
                    });
                },

                inputs: function (findChildren) {
                    return this.select(function (v) {
                        if (v == null) return $([]);
                        return findChildren ? $(v).find(':input') : $(v).filter(':input');
                    });
                },

                escape: function () {
                    var escaper = window.encodeURIComponent || window.encodeURI || window.escape;
                    return this.select(function (v) {
                        return escaper(v);
                    });
                },

                encode: function () {
                    var encoder = $('<div/>');
                    return this.select(function (v) {
                        return encoder.text(v).html();
                    });
                }
            }, 'each', function (v) {
                return v == null ? $([]) : $(v);
            }, defaults.jQueryInjectionMethods);
        }
    };

    var functions = {
        delay: function (interval, callback, passedArgs, replaceArgs) {
            /// <returns type="Function" />
            if (!isArray(passedArgs) && passedArgs != null) {
                // overload delay(interval, callback, replaceArgs, passedArgs...)
                return this.delay(interval, callback, slice.call(arguments, 3), passedArgs);
            }
            if (!passedArgs) passedArgs = [];
            var initArgs;
            var repeat = 1;
            var timerId;
            var started = false;
            var count = 0;
            var unlimit = false;
            var result = function () {
                if (repeat > 0 && !started) {
                    started = true;
                    if (replaceArgs) {
                        initArgs = passedArgs;
                    }
                    else {
                        initArgs = passedArgs.concat(toArray(arguments));
                    }
                    timerId = setTimeout(function () {
                        clearTimeout(timerId);
                        if (callback.apply(this, initArgs) === false) {
                            result.stop();
                            return;
                        }
                        count++;
                        if (unlimit || count <= repeat) {
                            timerId = setTimeout(arguments.callee, interval);
                        }
                        else {
                            result.stop();
                        }
                    }, interval);
                }
                return result;
            };

            result.info = function () {
                return {
                    count: count,
                    times: repeat,
                    started: started
                };
            };

            result.repeat = function (times) {
                if (arguments.length == 0) times = true;
                if (typeof times == 'boolean') {
                    unlimit = times;
                    times = 1;
                }
                repeat = parseInt(times);
                if (repeat <= 0) result.stop();
                return this;
            };

            result.stop = function () {
                clearTimeout(timerId);
                count = 0;
                started = false;
            };

            result.start = function () {
                return result.apply(result, arguments);
            };

            return result;
        },

        one: function (callback, def) {
            /// <returns type="Function" />
            var called = false;
            return function () {
                if (called) return def;
                called = true;
                return callback.apply(this, arguments);
            };
        },

        wrap: function (callback, wrapper, wrapResultOnly) {
            /// <returns type="Function" />
            if (wrapResultOnly) {
                return function () {
                    var result = callback.apply(this, arguments);
                    return wrapper.call(this, result, callback, arguments);
                };
            }
            return function () {
                return wrapper.apply(this, callback, arguments);
            };
        },

        overload: function (builder) {
            /// <returns type="Function" />
            var funcs;
            if (typeof builder == 'function') {
                funcs = [];
                builder(function () {
                    if (!arguments.length) return;
                    funcs.push({ params: slice.call(arguments, 0, arguments.length - 1), callback: arguments[arguments.length - 1] });
                }, function (def) {
                    if (def) funcs.push(def);
                });
            }
            else {
                funcs = toArray(arguments);
            }
            if (funcs.length == 0) return delegates.returnEmpty;
            return function () {
                return linq.overload(this, arguments, funcs);
            };
        }
    };

    // Static methods
    extend(linq, {
        overload: function (instance, args) {
            var def = null;
            args = toArray(args) || [];
            var funcs = arguments[2];
            if (!isArray(funcs)) funcs = slice.call(arguments, 2);
            for (var i = 0; i < funcs.length; i++) {
                var overload = funcs[i];
                if (!overload) continue;
                if (typeof overload == 'function') {
                    def = overload;
                    continue;
                }
                var params = overload.params || [];
                if (!params.length && args.length) continue;
                var found = true;
                var passedArgs = [];
                var validators = [];
                for (var j = 0; j < params.length; j++) {
                    var param = params[j];
                    if (typeof param == 'string' || typeof param == 'function') {
                        param = { type: param };
                    }
                    else if (isArray(param)) {
                        param = { type: param[0], optional: true, value: param[1] };
                    }
                    if (j >= args.length) {
                        if (!param.optional) {
                            found = false;
                            break;
                        }
                        passedArgs[j] = param.value;
                    }
                    else {
                        if (param.rest) {
                            var array = [];
                            for (var k = j; k < args.length; k++) {
                                if (isType(args[k], param.type, param.nullable)) {
                                    array[k - j] = args[k];
                                }
                                else {
                                    found = false;
                                    break;
                                }
                            }
                            if (found) {
                                passedArgs[j] = array;
                            }
                            else {
                                break;
                            }
                        }
                        else {
                            if (isType(args[j], param.type, param.nullable)) {
                                passedArgs[j] = args[j];
                            }
                            else {
                                found = false;
                                break;
                            }
                        }
                    }
                    validators[j] = param.validate;
                }

                if (!found) continue;
                // validate parameters
                if (validators.length) {
                    for (i = 0; i < validators.length; i++) {
                        var validator = validators[i];
                        if (validator) validator.call(instance, passedArgs[i]);
                    }
                }
                return overload.callback.apply(instance, passedArgs);
            }
            if (def) return def.apply(instance, args);
            return undef;
        },

        format: function (args, pattern) {
            if (typeof (args) == 'string') return this.format(slice.call(arguments, 1), args);
            args = args == null ? {} : args;
            return pattern.replace(/{{|}}|{((?:[^{}]|{{|}})+)}|<%([\w\W]+?)%>/g, function (g0, g1, g2) {
                if (g2) {
                    if (g2.charAt(0) == '=') g2 = '{{return ' + g2.substr(1) + '}}';
                    return new Function(defaults.formatUtilitiesParamName, defaults.globalizeParamName, g2).call(args, formatUtilities, win.Globalize);
                }
                if (!g1) return g0.charAt(0);
                g1 = g1.replace(/{{/g, '{').replace(/}}/g, '}');
                if (g1 == '@') return args;
                if (g1.charAt(0) == '?') {
                    var f = toFunction(g1.substr(1).replace(/@(\d+)\b|@(\w+)/g, function (h0, h1, h2) {
                        if (h2) return 'this.' + h2;
                        return 'this[' + h1 + ']';
                    }), ['u', 'g']);
                    return f.call(args, formatUtilities, win.Globalize);
                }
                var v = args[g1];
                while (v instanceof Function) v = v.call(args, g1);
                if (v && v.format instanceof Function) v = v.format();
                return v == null ? '' : v;
            });
        },

        obj: function (init) {
            /// <summary>
            /// Object builder
            /// </summary>
            /// <returns type="Function" />
            var result = init || {};
            for (var i = 1; i < arguments.length; i++) {
                if (arguments[i] != null) extend(result, arguments[i]);
            }
            return function (name, value) {
                if (arguments.length == 0) return result;
                if (typeof name == 'function') {
                    return name.apply(result, slice.call(arguments, 1));
                }
                else if (typeof name == 'string') {
                    if (arguments.length == 1) {
                        delete result[name];
                    }
                    else {
                        result[name] = value;
                    }
                }
                else {
                    for (var j = 0; j < arguments.length; j++) {
                        if (arguments[j] != null) extend(result, arguments[j]);
                    }
                }
                return arguments.callee;
            };
        },

        override: function (instance, methodName, newMethod) {
            if (instance) {
                var base = instance.__base;
                if (!base) {
                    base = instance.__base = function (method, args) {
                        var methods = arguments.callee.methods;
                        if (!methods) return undef;
                        method = methods[method];
                        return method ? method.apply(this, args || []) : undef;
                    };
                    base.methods = {};
                }
                base.methods[methodName] = instance[methodName];
                instance[methodName] = newMethod;
            }
            return this;
        },

        use: function (library) {
            if (typeof library == 'string') {
                var l = libs[library];
                if (l) extend(ex, typeof l == 'function' ? l() : l);
            }
            else if (library == true) {
                $.each(libs, function (n, v) {
                    extend(ex, typeof v == 'function' ? v() : v);
                });
            }
            else {
                extend(ex, library);
            }

            return this;
        },

        request: function (url, success, options) {
            if (url) {
                if (typeof url != 'string') return this.request(url.url, null, url);
                if (typeof success != 'function') return this.request(url, null, success);
                if (options == null) {
                    options = {
                        type: 'post',
                        dataType: 'json'
                    };
                }
                else if (typeof options == 'boolean') {
                    options = { type: options ? 'get' : 'post' };
                }
                else if (typeof options == 'string') {
                    options = { data: options, contentType: 'application/x-www-form-urlencoded' };
                }
                options.url = url;
                var oldSuccess = success || options.success;
                options.success = function (data) {
                    if (oldSuccess) oldSuccess.call(this, linq(data));
                };
                return $.ajax(options);
            }
            return false;
        },

        id: function (prefix, suffix) {
            if (prefix == null) prefix = '';
            if (suffix == null) suffix = '';
            return prefix.toString() + (win.top.___uniqueId = (parseInt(win.top.___uniqueId, 10) || 0) + 1) + suffix.toString();
        },

        destr: function (array, callback, context) {
            /// <summary>Destructuring assignment
            /// linq.des([1, 5, 6], function(a, b, c) { return a + b * 2 + c; } )
            /// linq.des([1, 5, 6], function(a, b, c) { return [b, c, a]; } )
            /// </summary>
            callback = toFunction(callback);
            if (callback) return callback.apply(context || this, toArray(array) || []);
            return undef;
        },

        apply: function (funcName, args) {
            var func = this[funcName];
            if (func instanceof Function) {
                return func.apply(this, toArray(args) || []);
            }
            return undef;
        },

        delegates: delegates,
        defaults: defaults,
        converters: converters,
        utils: {
            isArray: isArray,
            lambda: toFunction,
            isEnum: isEnumerable,
            isType: isType,
            toArray: toArray,
            wrap: function (callback) {
                callback = toFunction(callback);
                if (callback) {
                    function setParam(n, v) {
                        var fun = this;
                        if (fun === callback) fun = function () {
                            var args = toArray(arguments);
                            var values = arguments.callee.values.slice(0);
                            for (var i = 0; i < values.length; i++) {
                                var arg = values[i];
                                args[arg.index] = arg.value;
                            }
                            return callback.apply(this, args);
                        };
                        if (!fun.indexes) {
                            fun.indexes = {};
                            var match = /\(([^)]*)\)/.exec(callback.toString())[0].replace(/[\s()]+/g, '').split(',');
                            $.each(match, function (index, argName) {
                                fun.indexes[argName] = index;
                            });
                            fun.values = [];
                            fun.set = setParam;
                        }
                        var params = {};
                        if (arguments.length == 1) {
                            params = n;
                        }
                        else {
                            params[n] = v;
                        }
                        if (params) {
                            $.each(params, function (paramName, paramValue) {
                                if (paramName in fun.indexes) {
                                    fun.values.push({ index: fun.indexes[paramName], value: paramValue });
                                }
                            });
                        }
                        return fun;
                    }
                    callback.set = setParam;
                }
                return callback;
            }
        },

        func: functions
    });

    extend(linq, generators);

    extend(formatUtilities, {
        each: $.each,
        pad: function (value, number, string) {
            value = value == null ? '' : value.toString();
            number = parseInt(number, 10) || 0;
            if (number == 0) return value;
            if (string == null) string = ' ';
            if (number > 0) {
                return new Array(number + 1).join(string) + value;
            }
            return value + new Array(-number + 1).join(string);
        },
        format: linq.format
    });

    // jQuery extensions
    extend($.fn, {
        linq: function () {
            /// <returns type="Enumerable"/>
            return linq.jq(this);
        }
    });

    // export

    var old = {
        linq: win.linq,
        $from: win.$from,
        $L: win.$L,
        $F: win.$F
    };

    linq.noConflicts = function (instanceName) {
        win.$from = old.$from;
        win.$L = old.$L;
        win.$F = old.$F;
        win[instanceName] = linq;
    };

    function counter(name) {
        var startValue = 0;

        return {
            reset: function (start) {
                startValue = parseInt(start, 10) || 0;
                counters[name] = startValue;
            },

            start: function (start) {
                startValue = parseInt(start, 10) || 0;
                if (!name) {
                    counterStack.push(startValue);
                }

                return start;
            },

            stop: function () {
                counterStack.pop();
            },

            change: function (value) {
                value = parseInt(value, 10) || 0;
                if (value == 0) value = 1;
                value = this.current() + value;
                if (name) {
                    counters[name] = value;
                }
                else {
                    counterStack[counterStack.length - 1] = value;
                }
                return value;
            },

            current: function () {
                var value = parseInt(name ? counters[name] : counterStack[counterStack.length - 1], 10);
                if (isNaN(value)) value = startValue;
                return value;
            }
        };
    }

    win.counter = counter;

    $.each(['start', 'stop', 'change', 'current'], function (name, value) {
        counter[value] = function () {
            var context = counter();
            return context[value].apply(context, arguments);
        };
    });

    win.$L = win.linq = linq;
    win.$F = win.$from = linq.from;

    (function ($) {
        var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"': '\\"',
            '\\': '\\\\'
        },
        s = {
            'array': function (x) {
                var a = ['['], b, f, i, l = x.length, v;
                for (i = 0; i < l; i += 1) {
                    v = x[i];
                    f = s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a[a.length] = v;
                            b = true;
                        }
                    }
                }
                a[a.length] = ']';
                return a.join('');
            },
            'boolean': function (x) {
                return String(x);
            },
            'null': function (x) {
                return "null";
            },
            'number': function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            'object': function (x) {
                if (x) {
                    if (x instanceof Array) {
                        return s.array(x);
                    }
                    var a = ['{'], b, f, i, v;
                    for (i in x) {
                        v = x[i];
                        f = s[typeof v];
                        if (f) {
                            v = f(v);
                            if (typeof v == 'string') {
                                if (b) {
                                    a[a.length] = ',';
                                }
                                a.push(s.string(i), ':', v);
                                b = true;
                            }
                        }
                    }
                    a[a.length] = '}';
                    return a.join('');
                }
                return 'null';
            },
            'string': function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function (a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            }
        };

        $.toJSON = function (v) {
            var f = isNaN(v) ? s[typeof v] : s['number'];
            if (f) return f(v);
        };

        $.parseJSON = function (v, safe) {
            if (safe === undefined) safe = $.parseJSON.safe;
            if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v))
                return undefined;
            return eval('(' + v + ')');
        };

        $.parseJSON.safe = false;

    })(linq);

})(window, window.jQuery);

